/*******************************************************************************
 * Copyright (c) 2008 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Bartosz Michalik (bartosz.michalik@gmail.com)
 *******************************************************************************/
package org.eclipse.pde.internal.ui.editor.product;

import java.io.*;
import java.util.*;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.State;
import org.eclipse.pde.core.plugin.TargetPlatform;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.TargetPlatformHelper;
import org.eclipse.pde.internal.core.iproduct.*;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;

/**
 * config.ini builder
 * @author Bartosz Michalik
 *
 */
public class ConfigFileBuilder {
	public static final String PDE_AUTOGEN_MARKER = "pde autogen"; //$NON-NLS-1$
	private IProduct fProduct;
	private String[] fConfig;
	private State fState;
	private BundleDescription[] fBundleDescriptions;

	/**
	 * Builds config.ini file. Make sure that setters are called before calling this method.
	 * If state is not set default parameter is used.
	 * @param monitor - progress monitor
	 * @param autoBuild - if <code>true</code> pde auto-build marker is added to file 
	 * @return stream with the content of config.ini file
	 */
	public InputStream buildConfig(IProgressMonitor monitor, boolean autoBuild) throws IllegalStateException {
		monitor.subTask("Generate config file"); //$NON-NLS-1$
		ByteArrayOutputStream content = new ByteArrayOutputStream();
		PrintWriter writer = new PrintWriter(content);

		if (fConfig == null || fConfig.length < 3)
			throw new IllegalStateException("configuration not set"); //$NON-NLS-1$
		String location = getSplashLocation(fConfig[0], fConfig[1], fConfig[2]);
		if (autoBuild)
			writer.println("#" + PDE_AUTOGEN_MARKER + " please do not remove"); //$NON-NLS-1$ //$NON-NLS-2$
		writer.println("#Product Runtime Configuration File"); //$NON-NLS-1$
		writer.println();
		if (location != null)
			writer.println("osgi.splashPath=" + location); //$NON-NLS-1$
		writer.println("eclipse.product=" + fProduct.getId()); //$NON-NLS-1$
		writer.println("osgi.bundles.defaultStartLevel=4"); //$NON-NLS-1$

		if (fProduct.getCustomizationInfo().getUseCustomizations()) {
			String[] frameworkExtensions = fProduct.getCustomizationInfo().getFrameworkExtensions();
			if (frameworkExtensions != null && frameworkExtensions.length > 0) {
				writer.print("osgi.framework.extensions="); //$NON-NLS-1$
				for (int i = 0; i < frameworkExtensions.length; i++) {
					String extension = frameworkExtensions[i];
					writer.print(extension);
					if (i < frameworkExtensions.length - 1)
						writer.print(',');
				}
				writer.println();
			}
		}

		String bundleList = getPluginList(fConfig, TargetPlatform.getBundleList());
		writer.println("osgi.bundles=" + bundleList); //$NON-NLS-1$
		writer.close();
		return new ByteArrayInputStream(content.toByteArray());
	}

	private String getSplashLocation(String os, String ws, String arch) {
		ISplashInfo info = fProduct.getSplashInfo();
		String plugin = null;
		if (info != null) {
			plugin = info.getLocation();
		}
		if (plugin == null || plugin.trim().length() == 0)
			plugin = getBrandingPlugin();

		if (plugin == null)
			return null;

		StringBuffer buffer = new StringBuffer("platform:/base/plugins/"); //$NON-NLS-1$
		buffer.append(plugin.trim());

		State state = fState == null ? TargetPlatformHelper.getState() : fState;

		BundleDescription bundle = state.getBundle(plugin, null);
		if (bundle != null) {
			BundleDescription[] fragments = bundle.getFragments();
			for (int i = 0; i < fragments.length; i++) {
				String id = fragments[i].getSymbolicName();
				if (fProduct.containsPlugin(id)) {
					buffer.append(",platform:/base/plugins/"); //$NON-NLS-1$
					buffer.append(id);
				}
			}
		}
		return buffer.toString();
	}

	private String getPluginList(String[] config, String bundleList) {
		if (fProduct.useFeatures()) {
			// if we're using features and find simple configurator in the list, let's just default and use update configurator
			if (bundleList.indexOf("org.eclipse.equinox.simpleconfigurator") != -1) { //$NON-NLS-1$
				return TargetPlatformHelper.getDefaultBundleList();
			}
			return bundleList;
		}

		ICustomizationInfo cInfo = fProduct.getCustomizationInfo();
		List required = new LinkedList();
		if (cInfo.getUseCustomizations()) {
			required.addAll(Arrays.asList(cInfo.getRequiredBundles()));
			required.removeAll(Arrays.asList(cInfo.getFrameworkExtensions()));
			required.add(cInfo.getTargetPlugin());
		}

		StringBuffer buffer = new StringBuffer();

		// include only bundles that are actually in this product configuration
		Set initialBundleSet = new HashSet();
		StringTokenizer tokenizer = new StringTokenizer(bundleList, ","); //$NON-NLS-1$
		while (tokenizer.hasMoreTokens()) {
			String token = tokenizer.nextToken();
			int index = token.indexOf('@');
			String id = index != -1 ? token.substring(0, index) : token;
			if (fProduct.containsPlugin(id)) {
				if (buffer.length() > 0)
					buffer.append(',');
				buffer.append(id);
				if (required.contains(id)) {
					//force starting for required
					buffer.append("@1:start"); //$NON-NLS-1$
					required.remove(id);

				} else if (index != -1 && index < token.length() - 1)
					buffer.append(token.substring(index));
				initialBundleSet.add(id);
			}
		}

		if (!fProduct.containsPlugin("org.eclipse.update.configurator")) { //$NON-NLS-1$
			initialBundleSet.add("org.eclipse.osgi"); //$NON-NLS-1$

			Dictionary environment = new Hashtable(4);
			environment.put("osgi.os", config[0]); //$NON-NLS-1$
			environment.put("osgi.ws", config[1]); //$NON-NLS-1$
			environment.put("osgi.arch", config[2]); //$NON-NLS-1$
			environment.put("osgi.nl", config[3]); //$NON-NLS-1$

			BundleContext context = PDECore.getDefault().getBundleContext();
			//TODO add support for features based products
			if (fBundleDescriptions != null)
				for (int i = 0; i < fBundleDescriptions.length; i++) {
					BundleDescription bundle = fBundleDescriptions[i];
					String filterSpec = bundle.getPlatformFilter();
					try {
						if (filterSpec == null || context.createFilter(filterSpec).match(environment)) {
							String id = fBundleDescriptions[i].getSymbolicName();
							if (!initialBundleSet.contains(id)) {
								if (buffer.length() > 0)
									buffer.append(","); //$NON-NLS-1$
								buffer.append(id);
								if (required.contains(id)) {
									//force starting for required
									buffer.append("@1:start"); //$NON-NLS-1$
									required.remove(id);
								}

								// ensure core.runtime is always started
								if ("org.eclipse.core.runtime".equals(id)) { //$NON-NLS-1$
									buffer.append("@start"); //$NON-NLS-1$
								}
							}
						}
					} catch (InvalidSyntaxException e) {
					}
				}
		}

		// something horribly went wrong if we get there
		if (buffer.length() == 0)
			buffer.append(TargetPlatformHelper.getDefaultBundleList());

		if (!required.isEmpty()) {
			//last trial
			for (Iterator i = required.iterator(); i.hasNext();) {
				String id = (String) i.next();
				if (fProduct.containsPlugin(id)) {
					if (buffer.length() > 0)
						buffer.append(',');
					buffer.append(id);
					buffer.append("@1:start"); //$NON-NLS-1$
				} else { // TODO serve we have a problem
				}
			}
		}

		// if we have both, prefer update.configurator for now
		if (fProduct.containsPlugin("org.eclipse.update.configurator") && fProduct.containsPlugin("org.eclipse.equinox.simpleconfigurator")) //$NON-NLS-1$//$NON-NLS-2$
			return TargetPlatformHelper.getDefaultBundleList();

		return buffer.toString();
	}

	private String getBrandingPlugin() {
		int dot = fProduct.getId().lastIndexOf('.');
		return (dot != -1) ? fProduct.getId().substring(0, dot) : null;
	}

	/**
	 * 
	 * @param product - product for which config.file should be generated
	 */
	public ConfigFileBuilder(IProduct product) {
		if (product == null)
			throw new NullPointerException("parameter cannot be null"); //$NON-NLS-1$
		fProduct = product;
	}

	public void setState(State state) {
		fState = state;
	}

	public void setConfiguration(String[] config) {
		fConfig = config;
	}

	/**
	 * @param bundleDescriptions for computing osgi.bundles
	 */
	public void setBundleDescriptions(BundleDescription[] bundleDescriptions) {
		this.fBundleDescriptions = bundleDescriptions;
	}

}
